/*
 *  This file is part of jMoviepilot.
 *  Copyright (C) 2011 Tobias Roettger <toroettg@gmail.com>
 *
 *  jMoviepilot is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *  
 *  The project site can be found at http://code.google.com/p/jmoviepilot/
 */

package de.roettger_it.jmoviepilot.net;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.LinkedList;

import org.joda.time.LocalDate;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;

import de.roettger_it.jmoviepilot.util.Movie;
import de.roettger_it.jmoviepilot.util.MovieSearchResult;
import de.roettger_it.jmoviepilot.util.Neighbour;
import de.roettger_it.jmoviepilot.util.Neighbourhood;

/**
 * This Class handles requests to the moviepilot database.
 * JSON responses from the moviepilot database are converted via the google-gson library.
 * 
 * <p>It should not be used directly; use Moviepilot instead.</p>
 *
 * @author Tobias Roettger
 *
 */
public class MoviepilotConnection
{	
	private static String baseURL = "www.moviepilot.de/";
	private static String moviesURL = "movies/";
	private static String searchURL = "searches/";
	
	private static Gson gson = new GsonBuilder().registerTypeAdapter(LocalDate.class, new LocalDateDeserializer())
								                .registerTypeAdapter(LinkedList.class, new StringListDeserializer())
								                .registerTypeAdapter(Neighbourhood.class, new NeighbourhoodDeserializer())
								                .registerTypeAdapter(MovieSearchResult.class, new MovieSearchResultDeserializer())
								                .create();
	
	/**
	 * Sends a request to obtain information about a single movie to the moviepilot database.
	 * 
	 * @param identifier the moviepilot identifier of the movie to get the information for (mostly the movie name) 
	 * @param apiKey the api key that is used for the request
	 * 
	 * @return the requested Movie or null if none could be found
	 */
	public static Movie requestMovie(String identifier, String apiKey)
	{
		String response = handleRequest(baseURL + moviesURL + identifier + ".json", apiKey);
		
		Movie movie = gson.fromJson(response, Movie.class);
		
		return movie;
	}
	
	/**
	 * Sends a request to obtain information about a single movie from the moviepilot database by an alternative identifier (e.g. IMDB ID).
	 * 
	 * @param serviceName the service name to which the identifiere belongs to
	 * @param identifier the alternative identifier of the movie to get the information for
	 * @param apiKey the api key that is used for the request
	 * 
	 * @return the requested Movie or null if none could be found
	 */
	public static Movie requestMovie(String serviceName, String identifier, String apiKey)
	{
		String response = handleRequest(baseURL + moviesURL + serviceName + "-id-" + identifier + ".json", apiKey);
		
		Movie movie = gson.fromJson(response, Movie.class);
		
		return movie;
	}
	
	/**
	 * Sends a request to obtain the neighbourhood of a single movie to the moviepilot database.
	 * 
	 * @param identifier the moviepilot identifier of the movie to get the neighbourhood for (mostly the movie name) 
	 * @param apiKey the api key that is used for the request
	 * 
	 * @return the requested Neighbourhood or null if none could be found
	 */
	public static Neighbourhood requestMovieNeighbourhood(String identifier, String apiKey)
	{
		String response = handleRequest(baseURL + moviesURL + identifier + "/neighbourhood.json", apiKey);
		
		Neighbourhood neighbourhood = gson.fromJson(response, Neighbourhood.class);
		
		return neighbourhood;
	}
	
	/**
	 * Sends a request to obtain the neighbourhood of a single movie from the moviepilot database by an alternative identifier (e.g. IMDB ID).
	 * 
	 * @param serviceName the service name to which the identifiere belongs to
	 * @param identifier the alternative identifier of the movie to get the neighbourhood for
	 * @param apiKey the api key that is used for the request
	 * 
	 * @return the requested Neighbourhood or null if none could be found
	 */
	public static Neighbourhood requestMovieNeighbourhood(String serviceName, String identifier, String apiKey)
	{
		String response = handleRequest(baseURL + moviesURL + serviceName + "-id-" + identifier + "/neighbourhood.json", apiKey);
		
		Neighbourhood neighbourhood = gson.fromJson(response, Neighbourhood.class);
		
		return neighbourhood;
	}
	
	/**
	 * Searches the moviepilot database for a movie.
	 * 
	 * @param searchTerm the term that will be searched for
	 * @param apiKey the api key that is used for the request
	 * 
	 * @return the result of the search
	 */
	public static MovieSearchResult requestMovieSearch(String searchTerm, String apiKey)
	{
		String response = handleRequest(baseURL + searchURL + "movies.json", apiKey, "q=" + searchTerm);
		
		MovieSearchResult searchResult = gson.fromJson(response, MovieSearchResult.class);
		
		return searchResult;
	}
	
	/**
	 * Sends a JSON request via HTTP and recieves the JSON response as raw String
	 * 
	 * @param request the request that will be send
	 * @param apiKey the api key that is used for the request
	 * @param options the options that will be passed on with the request
	 * 
	 * @return the JSON response from moviepilot as String, or null in case of a malformed request (e.g. if an unknown identifier was used)
	 */
	private static String handleRequest(String request, String apiKey, String... options)
	{
		String response = null;
		
		try
		{
			StringBuffer optionBuffer = new StringBuffer();
			
			for(String option : options)
			{
				optionBuffer.append("&");
				optionBuffer.append(option);
			}
						
			URI uri = new URI("http", "//" + request + "?api_key=" + apiKey + optionBuffer.toString(), null);
			
			BufferedReader responseReader = new BufferedReader(new InputStreamReader(uri.toURL().openStream()));
		
			response = responseReader.readLine();

			responseReader.close();
		}
		catch(MalformedURLException mue)
		{
			mue.printStackTrace();
		}
		catch(FileNotFoundException fnfe)
		{
			//No result for request, nothing to do - will return null
		}
		catch(IOException ioe)
		{
			ioe.printStackTrace();
		}
		catch (URISyntaxException e)
		{
			e.printStackTrace();
		}
		
		return response;
	}
	
	/**
	 * This Class is used by gson to convert Dates into LocalDate instances.
	 * 
	 * @author Tobias Roettger
	 *
	 */
	private static class LocalDateDeserializer implements JsonDeserializer<LocalDate>
	{
		public LocalDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
		{
			return new LocalDate(json.getAsJsonPrimitive().getAsString());
		}
	}
	
	/**
	 * This Class is used by gson to convert CSVs into a list of Strings.
	 * 
	 * @author Tobias Roettger
	 *
	 */
	private static class StringListDeserializer implements JsonDeserializer<LinkedList<String>>
	{
		public LinkedList<String> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
		{
			LinkedList<String> results = new LinkedList<String>();
			
			for(String result : json.getAsJsonPrimitive().getAsString().split(","))
			{
				results.add(result);
			}
			
			return results;
		}
	}
	
	/**
	 * This Class is used by gson to convert Neighbourhoods correctly.
	 *  
	 * @author Tobias Roettger
	 *
	 */
	private static class NeighbourhoodDeserializer implements JsonDeserializer<Neighbourhood>
	{
		public Neighbourhood deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
		{			
			Neighbourhood resultNeighbourhood = new Neighbourhood();
			
			
			LinkedList<Neighbour> neighbourList = new LinkedList<Neighbour>();
			
			for(JsonElement result : json.getAsJsonObject().getAsJsonArray("item_neighbours"))
			{
				neighbourList.add(gson.fromJson(result, Neighbour.class));
			}
			
			resultNeighbourhood.setNeighbours(neighbourList);
			resultNeighbourhood.setTotalEntries(json.getAsJsonObject().getAsJsonPrimitive("total_entries").getAsInt());
						
			return resultNeighbourhood;
		}
	}
	
	/**
	 * This Class is used by gson to convert MovieSearchResults correctly.
	 *  
	 * @author Tobias Roettger
	 *
	 */
	private static class MovieSearchResultDeserializer implements JsonDeserializer<MovieSearchResult>
	{
		public MovieSearchResult deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
		{			
			MovieSearchResult searchResult = new MovieSearchResult();
			
			JsonPrimitive totalEntriesPrimitive = json.getAsJsonObject().getAsJsonPrimitive("total_entries");
			
			if(totalEntriesPrimitive == null)
			{
				//Circumvent bug in moviepilot api, which returns a dash instead underscore if no movies were found
				totalEntriesPrimitive = json.getAsJsonObject().getAsJsonPrimitive("total-entries");
			}
			
			searchResult.setTotalEntries(totalEntriesPrimitive.getAsInt());
			
			if(searchResult.getTotalEntries() == 0)
			{
				LinkedList<String> suggestionList = new LinkedList<String>();
				
				for(JsonElement result : json.getAsJsonObject().getAsJsonArray("suggestions"))
				{
					suggestionList.add(result.getAsString());
				}
				
				searchResult.setSuggestions(suggestionList);
			}
			else
			{
				LinkedList<Movie> movieList = new LinkedList<Movie>();
				
				for(JsonElement result : json.getAsJsonObject().getAsJsonArray("movies"))
				{
					movieList.add(gson.fromJson(result, Movie.class));
				}
				
				searchResult.setMovies(movieList);
			}
				
			return searchResult;
		}
	}
}
